package com.diy.sigmund.diycommon.spring.interceptor;

import com.diy.sigmund.diycommon.entity.base.exception.DiyRuntimeException;
import com.diy.sigmund.diycommon.entity.constant.CommonConstant;
import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.diy.sigmund.diycommon.entity.enumerate.AccessLimitTypeEnum;
import com.diy.sigmund.diycommon.entity.enumerate.ResponseEnum;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisDistributedLock;
import com.diy.sigmund.diycommon.middleware.cache.redis.RedisService;
import com.diy.sigmund.diycommon.spring.ContextHolder;
import com.diy.sigmund.diycommon.spring.anno.AccessLimit;
import com.diy.sigmund.diycommon.util.NetworkUtil;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * 访问限流拦截器
 *
 * @author ylm-sigmund
 * @since 2021/8/8 12:05
 */
public class AccessLimitInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(AccessLimitInterceptor.class);
    @Autowired
    private RedisService redisService;
    @Autowired
    private RedisDistributedLock redisDistributedLock;

    /**
     * preHandle是在请求执行前执行的
     *
     * @param request  request
     * @param response response
     * @param handler  handler
     * @return 返回true, postHandler和afterCompletion方法才能执行, 否则false为拒绝执行，起到拦截器控制作用
     * @throws Exception Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        log.info(LogConstant.SPRING_LOG, "访问限流拦截器处理 开始");
        /**
         * handler保存了本次请求的controller也就是接口方法的一些信息，如类，方法，参数等
         * 如果是一次静态资源的请求则该handler不应该是HandlerMethod的实现类
         * 判断是否是一个正常的接口，如果是则进行鉴权操作，不是则直接放行
         */
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        return handle(request, method);
    }

    private boolean handle(HttpServletRequest request, Method method) {
        if (!method.isAnnotationPresent(AccessLimit.class)) {
            return true;
        }
        // 获取方法上的注解
        AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
        // 如果标记了注解，则判断权限
        if (Objects.isNull(accessLimit)) {
            log.info("该方法未加访问限流注解，放行");
            return true;
        }
        int limit = accessLimit.limit();
        int sec = accessLimit.sec();
        AccessLimitTypeEnum accessLimitTypeEnum = accessLimit.accessLimitType();
        // 打印的是枚举name
        log.info("当前接口限流类型是={}", accessLimitTypeEnum);
        String uri = request.getRequestURI();
        String key = getKey(request, accessLimitTypeEnum, uri);

        // Redis缓存处理
        int firstNumber = 1;
        // 将 key 的值设为 value ，当且仅当 key 不存在
        final Boolean setNxResult = redisService.setNx(key, firstNumber, sec, TimeUnit.SECONDS);
        if (setNxResult) {
            log.info("{}访问第{}次，最大访问限流次数{}次", uri, firstNumber, limit);
            return true;
        }
        // 原子自增之后判断剩余生存时间是否小于0
        final long currentNumber = redisService.incr(key, 1);
        final Long ttl = redisService.ttl(key);
        // 如果key不存在，那么key的值会先被初始化为0，再执行incr操作，此时不会设置剩余生存时间，故ttl=-1
        if (ttl < 0) {
            redisService.del(key);
            log.info("访问限流接口{}缓存的有效期{}小于0，删除当前key", uri, ttl);
            throw new DiyRuntimeException(ResponseEnum.REQUEST_FREQUENT);
        }
        if (currentNumber < limit) {
            log.info("{}访问第{}次，最大访问限流次数{}次", uri, currentNumber, limit);
            return true;
        }
        if (currentNumber == limit) {
            log.info("{}访问第{}次，且等于最大访问限流次数{}次", uri, currentNumber, limit);
            return true;
        }
        log.info("{}访问第{}次，且已超过最大访问限流次数{}次", uri, currentNumber, limit);
        // todo 如果限流针对的是个人用户，将其拉入黑名单，待持久化
        if (Objects.equals(AccessLimitTypeEnum.USER, accessLimitTypeEnum)) {
            log.error("将该用户拉入黑名单={}", ContextHolder.getInstance().getCustomUser().getUserId());
        }
        throw new DiyRuntimeException(ResponseEnum.REQUEST_FREQUENT);
    }

    private String getKey(HttpServletRequest request, AccessLimitTypeEnum accessLimitTypeEnum, String uri) {
        String ipAddress = NetworkUtil.getIpAddress(request);
        String key;
        if (Objects.equals(AccessLimitTypeEnum.USER, accessLimitTypeEnum)) {
            key = CommonConstant.ACCESS_LIMIT_PREFIX + uri + "_" + ContextHolder.getInstance().getCustomUser().getUserId();
        } else if (Objects.equals(AccessLimitTypeEnum.GLOBAL, accessLimitTypeEnum)) {
            key = CommonConstant.ACCESS_LIMIT_PREFIX + uri;
        } else if (Objects.equals(AccessLimitTypeEnum.IP, accessLimitTypeEnum)) {
            key = CommonConstant.ACCESS_LIMIT_PREFIX + uri + "_" + ipAddress;
        } else {
            log.error("访问限流枚举不匹配");
            throw new DiyRuntimeException(ResponseEnum.INTERNAL_SERVER_ERROR);
        }
        return key;
    }


    /**
     * postHandler是在请求结束之后,视图渲染之前执行的,但只有preHandle方法返回true的时候才会执行
     *
     * @param request      request
     * @param response     response
     * @param handler      handler
     * @param modelAndView modelAndView
     * @throws Exception Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {

    }

    /**
     * afterCompletion是视图渲染完成之后才执行,同样需要preHandle返回true，
     *
     * @param request  request
     * @param response response
     * @param handler  handler
     * @param ex       Exception
     * @throws Exception Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        log.info(LogConstant.SPRING_LOG, "访问限流拦截器处理 结束");
    }
}
